<?php
/**
 * Created by PhpStorm.
 * User: caoyong
 * Date: 15/3/19
 * Time: 22:10
 */

namespace Caoyong\PHPCore;

use \Countable;
use \ArrayAccess;
use \JsonSerializable;
use \Closure;
use Caoyong\PHPCore\Interfaces\JsonableInterface;
use Caoyong\PHPCore\Interfaces\ArrayableInterface;
use \Exception;

class Collection implements Countable, ArrayAccess, JsonSerializable, JsonableInterface, ArrayableInterface
{
    /**
     * @var array
     */
    protected $items;

    /**
     * @var array
     */
    protected $original;

    /**
     * @param array $items
     */
    public function __construct(array $items = [])
    {
        $this->items = $this->original = $items;
    }

    /**
     * @return array
     */
    public function all()
    {
        return $this->items;
    }

    /**
     * @param mixed $name
     * @return bool
     */
    public function has($name)
    {
        return $this->keyExists($name);
    }

    /**
     * @param mixed $value
     * @param bool $strict
     * @return bool
     */
    public function hasValue($value, $strict = false)
    {
        return in_array($value, $this->items, $strict);
    }

    /**
     * @param mixed $name
     * @param mixed $default
     * @return mixed
     */
    public function get($name, $default = null)
    {
        if ($this->offsetExists($name)) {
            return $this->items[$name];
        }
        return $this->with($default);
    }

    /**
     * @param mixed $name
     * @param $value
     */
    public function set($name, $value)
    {
        $this->offsetSet($name, $value);
    }

    public function remove($offset)
    {
        if ($this->has($offset)) {
            unset($this->items[$offset]);
        }
    }

    /**
     * @param mixed $value
     */
    public function push($value)
    {
        $this->offsetSet(null, $value);
    }

    /**
     * @return mixed
     */
    public function first()
    {
        reset($this->items);
        return current($this->items);
    }

    /**
     * @return mixed
     */
    public function shift()
    {
        return array_shift($this->items);
    }

    /**
     * @return int
     * @internal param array $arg1 [,...]
     */
    public function unshift()
    {
        $args = func_get_args();
        foreach ($args as $arg) {
            array_unshift($this->items, $arg);
        }
        return $this->count();
    }

    /**
     * @return mixed
     */
    public function pop()
    {
        return array_pop($this->items);
    }

    /**
     * @return array
     */
    public function keys()
    {
        return array_keys($this->items);
    }

    /**
     * @param $name
     * @return bool
     */
    public function keyExists($name)
    {
        return array_key_exists($name, $this->items);
    }

    /**
     * @return mixed
     */
    public function end()
    {
        return end($this->items);
    }

    /**
     * @param callable $callback
     * @param bool $merge
     * @return $this|static
     */
    public function map(callable $callback, $merge = false)
    {
        if ($merge) {
            $this->items = array_map($callback, $this->items);
            return $this;
        }
        return new static(array_map($callback, $this->items));
    }

    /**
     * @return static
     * @throws Exception
     * @internal param array $arg1 [,...]
     */
    public function merge()
    {
        $args = func_get_args();
        $counter = 1;
        $items = $this->items;
        foreach ($args as $array) {
            if (false === is_array($array)) {
                throw new Exception('Argument #' . $counter . ' is not an array');
            }
            $counter++;
            $items = array_merge($this->items, $array);
        }

        return new static($items);
    }

    /**
     * @param bool $merge
     * @param int $sort_flags
     * @return $this|static
     */
    public function unique($merge = false, $sort_flags = SORT_STRING)
    {
        if ($merge) {
            $this->items = array_unique($this->items, $sort_flags);
            return $this;
        }
        return new static(array_unique($this->items, $sort_flags));
    }

    /**
     * @return array
     */
    public function values()
    {
        return array_values($this->items);
    }

    /**
     * @param null|int $flags
     * @return bool
     */
    public function ksort($flags = null)
    {
        return ksort($this->items, $flags);
    }

    /**
     * @return $this
     */
    public function shuffle()
    {
        $keys = $this->keys();
        shuffle($keys);
        $result = [];
        foreach ($keys as $key) {
            $result[$key] = $this->items[$key];
        }
        $this->items = $result;
        return $this;
    }

    /**
     * @param $column
     * @param null|mixed $index_key
     * @return mixed
     */
    public function column($column, $index_key = null)
    {
        //兼容5.5以下,函数体与heler中array_column一致，如果引用helper此段分支可注释
        if (false === function_exists('array_column')) {
            /**
             * @param array $array
             * @param $column
             * @param null $index_key
             * @return array
             */
            $func = function (array $array, $column, $index_key = null) {
                $result = [];
                foreach ($array as $value) {
                    $column_exists = array_key_exists($column, $value);

                    if (false === is_array($value) || false === $column_exists) {
                        continue;
                    }

                    $index_key_exists = is_null($index_key) ? false : array_key_exists($index_key, $value);

                    if (true === $index_key_exists && false === is_null($value[$index_key])) {
                        $result[$value[$index_key]] = $value[$column];
                    } else {
                        array_push($result, $value[$column]);
                    }
                }
                return $result;
            };
        } else {
            $func = 'array_column';
        }

        return $func($this->items, $column, $index_key);
    }

    /**
     * @return $this
     */
    public function resetOriginal()
    {
        $this->items = $this->original;
        return $this;
    }

    /**
     * @return int
     */
    public function count()
    {
        return count($this->items);
    }

    /**
     * @param int $options
     * @param int $depth
     * @return string
     */
    public function toJson($options = 0, $depth = 512)
    {
        return json_encode($this->items, $options, $depth);
    }

    /**
     * @return array
     */
    public function toArray()
    {
        return array_map(function ($value) {
            return $value instanceof ArrayableInterface ? $value->toArray() : $value;

        }, $this->items);
    }

    /**
     * @return array
     */
    public function JsonSerialize()
    {
        return $this->toArray();
    }

    /**
     * @param mixed|string $offset
     * @return bool
     */
    public function offsetExists($offset)
    {
        return array_key_exists($offset, $this->items);
    }


    /**
     * @param mixed|string $offset
     * @return mixed
     */
    public function offsetGet($offset)
    {
        return $this->items[$offset];
    }


    /**
     * @param mixed|string $offset
     * @param mixed|string $value
     */
    public function offsetSet($offset, $value)
    {
        if (is_null($offset)) {
            $this->items[] = $value;
        } else {
            $this->items[$offset] = $value;
        }
    }

    public function offsetUnset($offset)
    {
        unset($this->items[$offset]);
    }

    /**
     * @param mixed $value
     * @return mixed
     */
    protected function with($value)
    {
        return $value instanceof Closure ? $value() : $value;
    }

    /**
     * @param $items
     * @return mixed
     */
    protected function getArrayableItems($items)
    {
        if ($items instanceof Collection) {
            $items = $items->all();
        } elseif ($items instanceof ArrayableInterface) {
            $items = $items->toArray();
        }

        return $items;
    }

    /**
     * @return static
     */
    public function __clone()
    {
        return new static($this->original);
    }
}
